package top.wshape1.largefileupload.service.impl;

import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import top.wshape1.largefileupload.config.MinioProperty;
import top.wshape1.largefileupload.service.MinioService;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;

/**
 * @author Wshape1
 * @version 1.0
 * @description
 * @since 2025-01-24
 */

@Slf4j
@Service
public class MinioServiceImpl implements MinioService {

    private final MinioClient minioClient;

    private final String bucketName;

    /**
     * 上传文件的缓存  // TODO 正常情况下应使用 如Redis或者数据库等
     * key: md5
     * value: objectName
     */
    private final ConcurrentMap<String, String> secUploadCacheMap = new ConcurrentHashMap<>();

    public MinioServiceImpl(MinioClient minioClient, MinioProperty minioProperty) {
        this.minioClient = minioClient;
        this.bucketName = minioProperty.getBucketName();
    }

    @Override
    public String generateDownloadUrl(String objectName) {
        return "http://192.168.245.128:9000/" + bucketName + "/" + objectName;
    }

    public String checkFile(String fileMd5) {
        String objectName = secUploadCacheMap.get(fileMd5);
        if (objectName == null) {
            return null;
        }
        try {
            GetObjectResponse response = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            if (response == null) {
                return null;
            }
            return response.object();
        } catch (ErrorResponseException e) {
            if ("NoSuchKey".equals(e.errorResponse().code())) {
                secUploadCacheMap.remove(fileMd5);
            }
            return null;
        } catch (InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            return null;
        }
    }

    @Override
    public String checkChunk(String fileMd5, int chunkIndex) {
        String objectName = toChunkPath(fileMd5, chunkIndex);
        try {
            GetObjectResponse response = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            if (response == null) {
                return null;
            }
            return response.object();
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            return null;
        }
    }

    @Override
    public void uploadFile(String objectName, String fileMd5, InputStream inputStream, String contentType) {
        try {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .contentType(contentType)
                    .stream(inputStream, -1, 10485760)
                    .build());
            secUploadCacheMap.put(fileMd5, objectName);
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void uploadChunk(String fileMd5, int chunkIndex, InputStream inputStream) {
        String objectName = toChunkPath(fileMd5, chunkIndex);
        try {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(inputStream, -1, 10485760)
                    .build());
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void mergeChunks(String finalObjectName, String md5, Integer chunks) {
        List<ComposeSource> sources = Stream.iterate(0, i -> i + 1)
                .limit(chunks)
                .map(i -> ComposeSource.builder()
                        .bucket(bucketName)
                        .object(toChunkPath(md5, i))
                        .build())
                .toList();
        // 合并分片
        try {
            minioClient.composeObject(ComposeObjectArgs.builder()
                    .bucket(bucketName)
                    .object(finalObjectName)
                    .sources(sources)
                    .build());
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            log.info("合并分片失败");
            throw new RuntimeException(e);
        }

        // TODO 验证文件: 合并后文件和原文件的md5值是否一致

        // 删除分片
        List<DeleteObject> objects = Stream.iterate(0, i -> i + 1)
                .limit(chunks)
                .map(i -> new DeleteObject(toChunkPath(md5, i)))
                .toList();
        try {
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(objects)
                    .build());

            results.forEach(result -> {
                try {
                    result.get();
                } catch (Exception e) {
                    log.error("某个分片删除失败", e);
                }
            });
        } catch (Exception e) {
            log.error("删除分片失败", e);
        }

        // 添加缓存
        secUploadCacheMap.put(md5, finalObjectName);
    }

    private String toChunkPath(String fileMd5, int chunkIndex) {
        return fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/" + chunkIndex;
    }


}
